Ensemble actions

Posted on 2023-04-20 by

henrikvilhelmberglund

Usually an action interacts with a single element , however it's possible that we may want to have an action that interacts with a group of elements .

Here we have a few balls we can drop into a box to add text to the box. We have set up so we can't drop C into the first box and can't drop A into the second box.
A
B
C
<script>
	let dropzone1 = "";
	let dropzone2 = "";

	let canDrop1 = false;
	let canDrop2 = false;

	function onDragStart(event) {
		const text = event.target.innerText;
		event.dataTransfer.setData("text", text);
		canDrop1 = text !== "C";
		canDrop2 = text !== "A";
	}

	function onDragEnd(event) {
		canDrop1 = canDrop2 = false;
	}

	function onDrop1(event) {
		event.preventDefault();
		const data = event.dataTransfer.getData("text");
		dropzone1 += data;
	}
	function onDrop2(event) {
		event.preventDefault();
		const data = event.dataTransfer.getData("text");
		dropzone2 += data;
	}
	function onDragOver1(event) {
		if (canDrop1) {
			event.preventDefault();
		}
	}
	function onDragOver2(event) {
		if (canDrop2) {
			event.preventDefault();
		}
	}
</script>

<div class="flex">
	<div class="m-4 h-28 w-28  bg-gray-300" on:drop={onDrop1} on:dragover={onDragOver1}>
		{dropzone1}
	</div>
	<div class="m-4 h-28 w-28 bg-gray-300" on:drop={onDrop2} on:dragover={onDragOver2}>
		{dropzone2}
	</div>

	<div class="flex">
		<div
			draggable="true"
			on:dragstart={onDragStart}
			on:dragend={onDragEnd}
			class="grid h-8 w-8 place-items-center rounded-[50%] bg-blue-500 text-white">
			A
		</div>
		<div
			draggable="true"
			on:dragstart={onDragStart}
			on:dragend={onDragEnd}
			class="grid h-8 w-8 place-items-center rounded-[50%] bg-blue-500 text-white">
			B
		</div>
		<div
			draggable="true"
			on:dragstart={onDragStart}
			on:dragend={onDragEnd}
			class="grid h-8 w-8 place-items-center rounded-[50%] bg-blue-500 text-white">
			C
		</div>
	</div>
</div>

<style>
</style>

Next let's try using actions instead:

Here is the same thing but using actions. We use dragAndDropActions.js to export a function that creates our actions.
A
B
C
<script>
	import { getDragAndDropActions } from "./dragAndDropActions";

	// function that creates an action
	// good way to not enforce the name of what is being returned!
	const [drag1, drop1] = getDragAndDropActions();
	const [drag2, drop2] = getDragAndDropActions();

  let dropzone1 = "";
	let dropzone2 = "";
</script>

<div class="flex">
	<div
		class="m-4 h-28 w-28  bg-gray-300"
		use:drop1
		on:receivedDragData={(event) => (dropzone1 += event.detail)}>
		{dropzone1}
	</div>
	<div
		class="m-4 h-28 w-28 bg-gray-300"
		use:drop2
		on:receivedDragData={(event) => (dropzone2 += event.detail)}>
		{dropzone2}
	</div>

	<div class="flex">
		<div
			draggable="true"
			use:drag1={"A"}
			class="grid h-8 w-8 place-items-center rounded-[50%] bg-blue-500 text-white">
			A
		</div>
		<div
			draggable="true"
			use:drag1={"A"}
			use:drag2={"B"}
			class="grid h-8 w-8 place-items-center rounded-[50%] bg-blue-500 text-white">
			B
		</div>
		<div
			draggable="true"
			use:drag2={"C"}
			class="grid h-8 w-8 place-items-center rounded-[50%] bg-blue-500 text-white">
			C
		</div>
	</div>
</div>

<style>
</style>

Note that we don't need to have the pair of actions used in the same component.

In the next example we have a div and button that both light up when we click the button.

The elements use the same action which adds them all to a Set and thus they are all run when any action is triggered.
0
foo
<script>
	import Example2Other from "./Example2Other.svelte";
	import getMarkUpdateAction from "./example2";

	const markUpdate = getMarkUpdateAction();

	let count = 0;
</script>

<div use:markUpdate>
	{count}
</div>

<button use:markUpdate on:click={() => count++}>++</button>

<Example2Other action={markUpdate} />

<style>
</style>

This is only possible because we're creating a function that returns an action . This means that if several elements then use this action they are grouped .

Finally we have an action which we apply to many elements, then color them based on their value.

  1. 25
  2. 40
  3. 46
  4. 34
  5. 21
  6. 46
  7. 12
  8. 27
  9. 43
  10. 36
  11. 28
  12. 45
  13. 14
  14. 33
  15. 20
  16. 44
  17. 8
  18. 0
  19. 41
  20. 14
<script>
	import { getStatsAction, generateData } from "./example3";
	import { afterUpdate } from "svelte";

	let data = generateData();
	const statsAction = getStatsAction();

	let showLessThan20 = false;

	afterUpdate(() => {
		toggleShowLessThan20(showLessThan20);
		hideMoreThan20();
	});

	function hideMoreThan20() {
		statsAction.getMoreThan20().forEach((element) => {
			element.style.background = "transparent";
		});
	}

	function toggleShowLessThan20(showLessThan20) {
		if (showLessThan20) {
			statsAction.getLessThan20().forEach((element) => {
				element.style.background = "red";
			});
		} else {
			statsAction.getLessThan20().forEach((element) => {
				element.style.background = "transparent";
			});
		}
	}
</script>

<button
	on:click={() => {
		data = generateData();
	}}>Shuffle</button>
<label><input type="checkbox" bind:checked={showLessThan20} /> Toggle</label>
<ol>
	{#each data as item}
		<li use:statsAction.action={item}>{item}</li>
	{/each}
</ol>

An interesting thing here is our action isn't a function but instead an object with methods . This works too!